import {ObjectID} from "mongodb";
import PWSApp from "../../core/app";
import {Promise} from 'es6-promise';
import HomeBaseAction from "./act.base";
import {Magics} from "../../core/magics";
import {Request, Response} from "express";
import * as utils from "../../core/utils";
import {MongoColls} from "../../config/mongo";
import {ErrCode} from "../../core/error.code";
import UserModel from "../../models/mod.user";
import {RouteType, PayType, Role} from "../../core/defines";
import AddressModel, {
    AddressInfo,
} from "../../models/mod.address";
import ProductModel, {
    ProductInfo,
    ProductSearchType,
} from "../../models/mod.product";
import MsgModel, {
    MsgInfo,
    MsgType,
    MsgStatus,
} from "../../models/mod.msg";
import OrderModel, {
    Order,
    OrderStatus,
    OrderProduct,
    OrderDisplayTab,
    DirectPayParam,
} from "../../models/mod.order";

export default class OrderAction extends HomeBaseAction {
    private _msgMod: MsgModel;
    private _userMod: UserModel;
    private _orderMod: OrderModel;
    private _addrMod: AddressModel;
    private _productMod: ProductModel;

    constructor(app: PWSApp) {
        super(app);
        this._userMod = new UserModel(this.db.collection(MongoColls.USER));
        this._orderMod = new OrderModel(this.db.collection(MongoColls.ORDER));
        this._productMod = new ProductModel(this.db.collection(MongoColls.PRODUCT));
        this._msgMod = new MsgModel(this.db.collection(MongoColls.MSG));
        this._addrMod = new AddressModel(this.db.collection(MongoColls.ADDRESS));
    }

    name() {
        return `${this.app.name}OrderAction`;
    }

    protected _setRoutes() {
        this._setRoot('/api/order'); //need login
        this._route(RouteType.GET, '/preview', this._actOrderPreview);
        this._route(RouteType.GET, '/detail', this._actOrderDetail);
        this._route(RouteType.GET, '/list/:role', this._actOrderList);
        this._route(RouteType.POST, '/create', this._actOrderCreate);
        this._route(RouteType.POST, '/pay', this._actOrderPay);
        this._route(RouteType.POST, '/deliver', this._actOrderDeliver);
        this._route(RouteType.POST, '/receipt', this._actOrderReceipt);
        this._route(RouteType.POST, '/evaluate', this._actOrderEvaluate);
        this._route(RouteType.POST, '/cancel', this._actOrderCancel);
        this._route(RouteType.POST, '/delete', this._actOrderDelete);
    }

    private _actOrderPreview(req: Request, res: Response) {
        let {prods} = req.query;
        let args = prods && JSON.parse(prods);
        let products = [];
        Promise.resolve().then(() => {
            if (!utils.isArray(args)) {
                return Promise.reject({err_code: ErrCode.ArgsInvalid});
            }
            for (let arg of args) {
                if (!utils.isObjectId(arg['prod_id']) || !utils.isPositiveNum(arg['num'])) {
                    return Promise.reject({err_code: ErrCode.ArgsInvalid});
                }
            }

            let tasks = [];
            for (let arg of args) {
                let filter = {_id: utils.toObjectId(arg['prod_id'])};
                tasks.push(this._productMod.checkExist(filter, {type: ProductSearchType.ORDER_PREVIEW}));
            }
            return Promise.all(tasks);
        }).then((prods: Array<ProductInfo>) => {
            if (!(prods && args.length === prods.length)) {
                return Promise.reject({err_code: ErrCode.ProdNotFound});
            }
            prods.forEach((e, idx) => {
                e.trans['num'] = args[idx].num;
                products.push(e);
            });
            let sess = req.session[Magics.SessUser] || {};
            return this._addrMod.getUserAddr({userId: utils.toObjectId(sess['_id'])});
        }).then((addr: AddressInfo) => {
            let order = {prods: []};
            products.forEach((e) =>  {
                order.prods.push({
                    prod_id: e._id,
                    num: e.trans.num,
                    price: e.trans.act_price || e.trans.price
                });
            });
            req.session[Magics.SessPreOrder] = order;
            res.send({
                addr,
                prods: products,
                err_code: ErrCode.Success
            });
        }).catch((err) => {
            this._errHandler(req, res, err);
        });
    }

    private _actOrderDetail(req: Request, res: Response){
        let {id} = req.query;
        Promise.resolve().then(() => {
            if (!utils.isObjectId(id)) {
                return Promise.reject({err_code: ErrCode.ArgsInvalid});
            }
            let sessUser = req.session[Magics.SessUser] || {};

            let filter = {
                _id: utils.toObjectId(id),
                user_id: utils.toObjectId(sessUser['_id'])
            };
            return this._orderMod.checkExist(filter, Role.BUYER);
        }).then((order) => {
            res.send({
                order,
                err_code: ErrCode.Success
            });
        }).catch((err) => {
            this._errHandler(req, res, err);
        });
    }

    private _actOrderList(req: Request, res: Response) {
        let role = req.params.role;
        let {tab, page} = req.query;
        Promise.resolve().then(() => {
            let invalid = !utils.isBelongToObj(role, Role) || !utils.isBelongToObj(tab, OrderDisplayTab);
            if(invalid){
                 return Promise.reject( { err_code : ErrCode.ArgsInvalid } );
            }
            let sess = req.session[Magics.SessUser] || {};
            let filter = {
                tab: <OrderDisplayTab>utils.toInt(tab) || 0,
                role: <Role>utils.toInt(role),
                user_id: utils.toObjectId(sess['_id']),
                page: page ? utils.toInt(page) : 0,
                limit: Magics.HomeOrderItemCount
            };
            return this._orderMod.getOrders(filter);
        }).then((data) => {
            res.send({
                orders: data['orders'],
                total_page: data['totalPage'],
                total_count: data['totalCount'],
                err_code: ErrCode.Success,
            });
        }).catch((err) => {
            this._errHandler(req, res, err);
        });
    }

    private _actOrderCreate(req: Request, res: Response) {
        let {addr_id, pay_type, log, prods} = req.body;
        let userId: ObjectID;
        let addr: AddressInfo;
        let prodInfo;
        let sess = req.session[Magics.SessUser] || {};
        let preOrder = req.session[Magics.SessPreOrder] || {};
        let prods = preOrder['prods'] || prods || [];
        Promise.resolve().then(() => {
            if (!utils.isArray(prods) || !prods.length || !utils.isBelongToObj(pay_type, PayType)
                || !utils.isObjectId(addr_id)) {
                return Promise.reject({err_code: ErrCode.ArgsInvalid});
            }
            for (let arg of prods) {
                if (!utils.isObjectId(arg['prod_id']) || !utils.isPositiveNum(arg['num'])) {
                    return Promise.reject({err_code: ErrCode.ArgsInvalid});
                }
            }

            userId = utils.toObjectId(sess['_id']);
            let tasks = [];
            for (let arg of prods) {
                let filter = {_id: utils.toObjectId(arg['prod_id'])};
                tasks.push(this._productMod.checkExist(filter));
            }
            return Promise.all(tasks);
        }).then((results: Array<ProductInfo>) => {
            if (!(results && results.length === prods.length)) {
                return Promise.reject({err_code: ErrCode.ProdNotFound});
            }
            prodInfo = this._productMod.regroupByShop(results);
            return this._addrMod.getUserAddr({userId});
        }).then((result: AddressInfo) => {
            if (!result) {
                return Promise.reject({err_code: ErrCode.UserAddrNotFound});
            }
            addr = result;

            let tasks = [];
            for (let key in prodInfo) {
                let amount: number = 0;
                let products: Array<OrderProduct> = [];
                prodInfo[key].forEach(function (e: ProductInfo) {
                    let count = 1;
                    products.push({
                        id: e._id,
                        thumb: e.base.thumb,
                        name: e.base.name,
                        cate: e.base.cate,
                        sub_cate: e.base.sub_cate,
                        price: e.trans.price,
                        num: count,
                        amount: e.trans.price * count,
                        ship_free: e.trans.ship_free,
                        log: e.trans.log,
                    });
                    amount += e.trans.price * count;
                });
                let shop = prodInfo[key][0]['shop'];
                let order: Order = {
                    buyer: {
                        id: userId,
                        wechat: <string>sess['wechat']
                    },
                    shop: {
                        id: <ObjectID>shop.id,
                        name: <string>shop.name,
                        phone: <string>shop.phone,
                        telephone: <string>shop.telephone,
                        loc: <Object>shop.loc,
                        address: <string>shop.address,
                        log: <string>shop.log || '',
                    },
                    order_no: utils.transNo(),
                    trans: {
                        products: products,
                        favorable: {
                            voucher: [],
                            amount: 0
                        },
                        amount: amount,
                        payment: amount,
                        pay_type: utils.toInt(pay_type),
                        log: <string>log,
                    },
                    delivery: {
                        buyer: {
                            id: addr._id,
                            name: addr.name,
                            phone: addr.phone,
                            loc: addr.loc,
                            address: addr.address
                        }
                    },
                    deleted: {
                        buyer: false,
                        vendor: false,
                    },
                    audit_log: [{
                        from: OrderStatus.CREATED,
                        to: OrderStatus.CREATED,
                        staff: sess['name'],
                        time: new Date()
                    }],
                    status: OrderStatus.CREATED,
                    time: new Date()
                };
                tasks.push(this._orderMod.createOrder(order));
            }
            return Promise.all(tasks);
        }).then(() => {
            delete req.session[Magics.SessPreOrder];
            res.send({err_code: ErrCode.Success});
        }).catch((err) => {
            this._errHandler(req, res, err);
        });
    }

    private _actOrderPay(req: Request, res: Response) {
        let {id, pay_type} = req.body;
        Promise.resolve().then(() => {
            if (!utils.isObjectId(id) || !utils.isBelongToObj(pay_type, PayType)) {
                return Promise.reject({err_code: ErrCode.ArgsInvalid});
            }
            let filter = {_id: utils.toObjectId(id)};
            return this._orderMod.checkExist(filter, Role.ADMIN);
        }).then((order: Order) => {
            if (!order) {
                return Promise.reject({err_code: ErrCode.OrderNotFound});
            }
            if (OrderStatus.CREATED != order.status) {
                return Promise.reject({err_code: ErrCode.OrderNotNeedToPay});
            }

            let option: DirectPayParam = {
                pay_type: <PayType>utils.toInt(pay_type),
                subject: order.trans.products[0].name,
                amount: order.trans.buyer_pay,
                trans_id: order.trans_id,
            };
            return this._directPay(req, res, option);
        }).then(() => {
            res.send({err_code: ErrCode.Success});
        }).catch((err) => {
            this._errHandler(req, res, err);
        });
    }

    private _directPay(req: Request, res: Response, options: DirectPayParam): Promise<any> {
        let {pay_type, subject, amount, trans_id} = options;
        return;//this._payUtils.hcPay( req , res , options )
    }

    private _actOrderDeliver(req: Request, res: Response) {

        Promise.resolve().then(() => {
            let {id} = req.body;
            if (!utils.isObjectId(id)) {
                return Promise.reject({err_code: ErrCode.ArgsInvalid});
            }
            let filter = {_id: utils.toObjectId(id)};
            return this._orderMod.checkExist(filter, Role.ADMIN);
        }).then((order: Order) => {
            if (!order) {
                return Promise.reject({err_code: ErrCode.OrderNotFound});
            }
            if (OrderStatus.DELIVING != order.status) {
                return Promise.reject({err_code: ErrCode.OrderStatusErr});
            }

            let sess = req.session[Magics.SessUser] || {};
            let filter = {
                _id: order._id,
                user_id: utils.toObjectId(sess['_id'])
            };
            return this._orderMod.deliver(filter);
        }).then(() => {
            res.send({err_code: ErrCode.Success});
        }).catch((err) => {
            this._errHandler(req, res, err);
        });
    }

    private _actOrderReceipt(req: Request, res: Response) {
        Promise.resolve().then(() => {
            let {id} = req.body;
            if (!utils.isObjectId(id)) {
                return Promise.reject({err_code: ErrCode.ArgsInvalid});
            }
            let filter = {_id: utils.toObjectId(id)};
            return this._orderMod.checkExist(filter, Role.ADMIN);
        }).then((order: Order) => {
            if (!order) {
                return Promise.reject({err_code: ErrCode.OrderNotFound});
            }
            if (OrderStatus.RECEIPTING != order.status) {
                return Promise.reject({err_code: ErrCode.OrderStatusErr});
            }

            let sess = req.session[Magics.SessUser] || {};
            let filter = {
                _id: order._id,
                user_id: utils.toObjectId(sess['_id'])
            };
            return this._orderMod.receipt(filter);
        }).then(() => {
            res.send({err_code: ErrCode.Success});
        }).catch((err) => {
            this._errHandler(req, res, err);
        });
    }

    private _actOrderEvaluate(req: Request, res: Response) {
        let {id, log} = req.body;
        Promise.resolve().then(() => {
            if (!utils.isObjectId(id) || !log) {
                return Promise.reject({err_code: ErrCode.ArgsInvalid});
            }
            let filter = {_id: utils.toObjectId(id)};
            return this._orderMod.checkExist(filter, Role.ADMIN);
        }).then((order: Order) => {
            if (!order) {
                return Promise.reject({err_code: ErrCode.OrderNotFound});
            }
            if (OrderStatus.SUCCESS != order.status) {
                return Promise.reject({err_code: ErrCode.OrderStatusErr});
            }

            let sess = req.session[Magics.SessUser] || {};
            let msg: MsgInfo = {
                type: MsgType.PRODUCT,
                status: MsgStatus.CREATE,
                content: log,
                from: utils.toObjectId(sess['_id']),
                to: order.vendor.id,
                time: new Date()
            };
            return this._msgMod.createMsg(msg);
        }).then(() => {
            res.send({err_code: ErrCode.Success});
        }).catch((err) => {
            this._errHandler(req, res, err);
        });
    }

    private _actOrderCancel(req: Request, res: Response) {
        Promise.resolve().then(() => {
            let {id} = req.body;
            if (!utils.isObjectId(id)) {
                return Promise.reject({err_code: ErrCode.ArgsInvalid});
            }
            let filter = {_id: utils.toObjectId(id)};
            return this._orderMod.checkExist(filter, Role.ADMIN);
        }).then((order: Order) => {
            if (!order) {
                return Promise.reject({err_code: ErrCode.OrderNotFound});
            }
            if (OrderStatus.CREATED != order.status) {
                return Promise.reject({err_code: ErrCode.OrderStatusErr});
            }

            let sess = req.session[Magics.SessUser] || {};
            let filter = {
                _id: order._id,
                user_id: utils.toObjectId(sess['_id'])
            };
            return this._orderMod.cancel(filter);
        }).then(() => {
            res.send({err_code: ErrCode.Success});
        }).catch((err) => {
            this._errHandler(req, res, err);
        });
    }

    private _actOrderDelete(req: Request, res: Response) {
        Promise.resolve().then(() => {
            let {id} = req.body;
            if (!utils.isObjectId(id)) {
                return Promise.reject({err_code: ErrCode.ArgsInvalid});
            }
            let filter = {_id: utils.toObjectId(id)};
            return this._orderMod.checkExist(filter, Role.ADMIN);
        }).then((order: Order) => {
            if (!order) {
                return Promise.reject({err_code: ErrCode.OrderNotFound});
            }
            if (OrderStatus.SUCCESS != order.status) {
                return Promise.reject({err_code: ErrCode.OrderStatusErr});
            }

            let sess = req.session[Magics.SessUser] || {};
            let filter = {
                _id: order._id,
                user_id: utils.toObjectId(sess['_id'])
            };

            let role = <Role>utils.toInt(sess['role']);
            switch (role) {
                case Role.BUYER:
                    return this._orderMod.deleteBuyer(filter);
                case Role.VENDOR:
                    return this._orderMod.deleteVendor(filter);
                default:
                    return Promise.reject({err_code: ErrCode.Forbidden});
            }
        }).then(() => {
            res.send({err_code: ErrCode.Success});
        }).catch((err) => {
            this._errHandler(req, res, err);
        });
    }

}